msg_tool\scripts\yuris/
yslb.rs

1//!Yu-Ris YSLB(labels) file (.ybn)
2use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::encoding::*;
6use crate::utils::struct_pack::*;
7use anyhow::Result;
8use msg_tool_macro::*;
9use serde::{Deserialize, Serialize};
10use std::io::{Read, Seek, Write};
11
12#[derive(Debug, StructUnpack, StructPack, Deserialize, Serialize)]
13pub(super) struct Label {
14    #[pstring(u8)]
15    pub name: String,
16    pub id: u32,
17    pub offset: u32,
18    pub script_index: u16,
19    #[serde(skip)]
20    padding: u16,
21}
22
23#[derive(Debug, StructUnpack, StructPack, Deserialize, Serialize)]
24pub(super) struct YSLBData {
25    pub version: u32,
26    #[serde(skip)]
27    num_labels: u32,
28    #[fvec = 0x100]
29    #[serde(skip)]
30    /// label_range_start_indexes[N] = index of first label with ID >= (N << 24)
31    label_range_start_indexes: Vec<u32>,
32    #[pack_vec_len(self.num_labels)]
33    #[unpack_vec_len(num_labels)]
34    pub labels: Vec<Label>,
35}
36
37#[derive(Debug)]
38pub struct YSLBBuilder {}
39
40impl YSLBBuilder {
41    /// Creates a new instance of `YSLBBuilder`
42    pub const fn new() -> Self {
43        YSLBBuilder {}
44    }
45}
46
47impl ScriptBuilder for YSLBBuilder {
48    fn default_encoding(&self) -> Encoding {
49        Encoding::Cp932
50    }
51
52    fn build_script(
53        &self,
54        buf: Vec<u8>,
55        _filename: &str,
56        encoding: Encoding,
57        _archive_encoding: Encoding,
58        config: &ExtraConfig,
59        _archive: Option<&Box<dyn Script>>,
60    ) -> Result<Box<dyn Script + Send + Sync>> {
61        Ok(Box::new(YSLB::new(MemReader::new(buf), encoding, config)?))
62    }
63
64    fn extensions(&self) -> &'static [&'static str] {
65        &["ybn"]
66    }
67
68    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
69        if buf_len >= 4 && buf.starts_with(b"YSLB") {
70            return Some(20);
71        }
72        None
73    }
74
75    fn script_type(&self) -> &'static ScriptType {
76        &ScriptType::YurisYSLB
77    }
78
79    fn can_create_file(&self) -> bool {
80        true
81    }
82
83    fn create_file<'a>(
84        &'a self,
85        filename: &'a str,
86        writer: Box<dyn WriteSeek + 'a>,
87        encoding: Encoding,
88        file_encoding: Encoding,
89        config: &ExtraConfig,
90    ) -> Result<()> {
91        create_file(
92            filename,
93            writer,
94            encoding,
95            file_encoding,
96            config.custom_yaml,
97        )
98    }
99}
100
101#[derive(Debug)]
102pub struct YSLB {
103    pub(super) data: YSLBData,
104    custom_yaml: bool,
105}
106
107impl YSLB {
108    pub fn new<T: Read + Seek>(
109        mut reader: T,
110        encoding: Encoding,
111        config: &ExtraConfig,
112    ) -> Result<Self> {
113        let mut sig = [0; 4];
114        reader.read_exact(&mut sig)?;
115        if &sig != b"YSLB" {
116            anyhow::bail!("Unsupported YSLB file.");
117        }
118        let data = YSLBData::unpack(&mut reader, false, encoding, &None)?;
119        Ok(Self {
120            data,
121            custom_yaml: config.custom_yaml,
122        })
123    }
124}
125
126impl Script for YSLB {
127    fn default_output_script_type(&self) -> OutputScriptType {
128        OutputScriptType::Custom
129    }
130
131    fn is_output_supported(&self, output: OutputScriptType) -> bool {
132        matches!(output, OutputScriptType::Custom)
133    }
134
135    fn default_format_type(&self) -> FormatOptions {
136        FormatOptions::None
137    }
138
139    fn custom_output_extension(&self) -> &'static str {
140        if self.custom_yaml { "yaml" } else { "json" }
141    }
142
143    fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> {
144        let s = if self.custom_yaml {
145            serde_yaml_ng::to_string(&self.data)
146                .map_err(|e| anyhow::anyhow!("Failed to serialize to YAML: {}", e))?
147        } else {
148            serde_json::to_string_pretty(&self.data)
149                .map_err(|e| anyhow::anyhow!("Failed to serialize to JSON: {}", e))?
150        };
151        let mut writer = crate::utils::files::write_file(filename)?;
152        let s = encode_string(encoding, &s, false)?;
153        writer.write_all(&s)?;
154        writer.flush()?;
155        Ok(())
156    }
157
158    fn custom_import<'a>(
159        &'a self,
160        custom_filename: &'a str,
161        file: Box<dyn WriteSeek + 'a>,
162        encoding: Encoding,
163        output_encoding: Encoding,
164    ) -> Result<()> {
165        create_file(
166            custom_filename,
167            file,
168            encoding,
169            output_encoding,
170            self.custom_yaml,
171        )
172    }
173}
174
175fn create_file<'a>(
176    custom_filename: &'a str,
177    mut writer: Box<dyn WriteSeek + 'a>,
178    encoding: Encoding,
179    output_encoding: Encoding,
180    yaml: bool,
181) -> Result<()> {
182    let input = crate::utils::files::read_file(custom_filename)?;
183    let s = decode_to_string(output_encoding, &input, true)?;
184    let mut data: YSLBData = if yaml {
185        serde_yaml_ng::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse YAML: {}", e))?
186    } else {
187        serde_json::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse JSON: {}", e))?
188    };
189    data.num_labels = data.labels.len() as u32;
190    data.label_range_start_indexes.resize(0x100, 0);
191    let max = data.num_labels as usize;
192    let mut i = 0;
193    for j in 0..256 {
194        loop {
195            if i >= max {
196                data.label_range_start_indexes[j] = max as u32;
197                break;
198            } else {
199                let lab = data.labels[i].id >> 24;
200                if lab as usize >= j {
201                    data.label_range_start_indexes[j] = i as u32;
202                    break;
203                }
204                i += 1;
205            }
206        }
207    }
208    writer.write_all(b"YSLB")?;
209    data.pack(&mut writer, false, encoding, &None)?;
210    Ok(())
211}